#include "tlbCMP.h"
#include "core.h"

static address_t nil;


TLBCMP::TLBCMP(uint32_t threads,uint32_t lineSize,uint32_t pageSize,
        uint32_t pagesInRange,uint32_t TLB1Size, uint32_t TLB2Size,
        uint32_t TLB1Nways, uint32_t TLB2Nways):
    _core(threads),_pthread(threads)
{
    _threads=threads;
    _pageSize=pageSize;
    _lineSize=lineSize;
    _TLB1Size=TLB1Size;
    _TLB2Size=TLB2Size;
    _TLB1Nways=TLB1Nways;
    _TLB2Nways=TLB2Nways;
    _pagesInRange=pagesInRange;
    pthread_mutex_init(&_mutexDiDi,NULL);

    for (int i=0;i<threads;i++) {
        _core[i] = new Core(*this,lineSize,pageSize,TLB1Size,TLB1Nways);
    }

    if (_TLB2Size>0) {
        uint32_t assocBits=0,rangeBits=0,setBits=0;
        uint32_t sets=_TLB2Size/lineSize;
        for(int i=0;i<sizeof(lineSize)*8;i++) {
              if ((sets>>i)>0)
                  setBits=i;
              if ((lineSize>>i)>0)
                  _blockBits=i;
              if ((_pagesInRange>>i)>0)
                  rangeBits=i;
              if ((TLB2Nways>>i)>0)
                   assocBits=i;
        }
        // mask to apply to obtain the num of page in directory Range
        _maskBlock   = ((1 << rangeBits) - 1) << (assocBits+_blockBits+setBits);
        _blockBits =assocBits+_blockBits+setBits;
        lineSize*=_pagesInRange;
        _TLB2[0] =  new CacheWriteBack<address_t> (_TLB2Size,TLB2Nways,lineSize,true,_pagesInRange);
    }

    /* L2 TLB stats */
    _TLB2Hits=_TLB2HitInvalidate=_TLB2Accesses=_TLB2Evicted=0;
    _TLB2Misses=_TLB2Replacements=_TLB2WriteBacks =0;

    _current_threads=0;
    _app=0;
}

TLBCMP::~TLBCMP()
{
    if (_TLB2Size>0) {
        delete _TLB2[0];
    }

    for (int i=0;i<_threads;i++)
        delete  _core[i];

    _appThreads.clear();
}

void
TLBCMP::reset()
{
    _app=_current_threads=0;
    for (int i=0;i<_threads;i++) {
        _core[i]->reset();
    }
    /* L2 TLB stats */
    _TLB2Hits=_TLB2HitInvalidate=_TLB2Accesses=_TLB2Evicted=0;
    _TLB2Misses=_TLB2Replacements=_TLB2WriteBacks =0;
}

void
TLBCMP::setAppTrace(Trace *t,uint32_t threads)
{
    // add new traceReader of of a new application
    for(int i=0;i<threads;i++) {
        _core[_current_threads]->setTrace(t,_app,i);
        _current_threads++;
    }
    _appThreads.push_back(threads);
    _app++;
}

void
TLBCMP::startCore(int core)
{
    pthread_create(&_pthread[core], NULL, Core::execute,_core[core]);
}

void
TLBCMP::execute()
{
    uint32_t *error;

    for (int i=0;i<_threads;i++)
        startCore(i);
    // wait for all threads to finish

    for (int i=0;i<_threads;i++)
        pthread_join(_pthread[i],(void **)&error);
}

address_t
TLBCMP::removeVPID(address_t addr)
{
    // remove appid from high address part
    addr= (0xF1FFFFFFFFFFFFFF & addr);
    //return (addr-(uint64_t)(appid & 0x07)<<57);
    // application is useless
    return addr;
}

address_t
TLBCMP::appendVPID(address_t addr,uint32_t appid)
{
    // we use the highest part (three bits) of address
    // to store the application pid
    addr= (0xF1FFFFFFFFFFFFFF & addr);
    addr=addr+(((uint64_t)(appid & 0x07))<<57);
    return addr;
}

uint32_t
TLBCMP::getApplicationVPID(address_t addr)
{
    // application is stored in the previous to highest byte
    //  3 bits
    return (uint32_t)(0x07 & (addr.get()>>57));
}

void
TLBCMP::evictTLB1Range(uint32_t appid,address_t evictPage,Directory dirEvict)
{
    evictPage=this->removeVPID(evictPage);

    for (int i=0;i<_threads;i++) {
        if (_core[i]->getApp()==appid) {
            address_t basePage = evictPage;
            if (_pagesInRange>1)
                basePage= resetPageInRange(basePage);
            for (int j=0;j<_pagesInRange;j++) {
                // Error en el directorio
                // Revisarlo, no se guarda simpre?
                //if (dirEvict.get(i,j)) {
                    _core[i]->flushPage(basePage);
                //}
                // Change the Page in the range
                basePage=setPageInRange(basePage,j);
            }
        }
    }
}

uint32_t
TLBCMP::getPageInDirectory(address_t page)
{
        if (_pagesInRange>1)
            return ((uint32_t) (_maskBlock & page.get())>>_blockBits);
        else
            return 0;
}

address_t
TLBCMP::resetPageInRange(address_t addr)
{
    uint64_t mask2=~((uint64_t)0x7<<_blockBits);
    return addr & mask2;
}

address_t
TLBCMP::setPageInRange(address_t addr,uint32_t page)
{
    addr=resetPageInRange(addr);
    return addr | ((uint64_t)page<<_blockBits);
}

// Eviction from threadid has been done
// Delete the entry from DiDi if its the last entry
void
TLBCMP::evictDiDi(uint32_t appid,address_t evictPage,uint32_t threadid)
{
    if (_TLB2Size>0) {
        // add Virtual PID to address tag
        evictPage=this->appendVPID(evictPage,appid);

        uint32_t page = getPageInDirectory(evictPage);
        pthread_mutex_lock(&_mutexDiDi);
        uint32_t count = _TLB2[0]->flushDirEntry(evictPage,threadid,page);
        if (count==0) {
            _TLB2[0]->flush(evictPage);
        }
        pthread_mutex_unlock(&_mutexDiDi);
    }
}

void
TLBCMP::addEntryDiDi(uint32_t appid,address_t addr,uint32_t threadid)
{
    address_t evictPage;
    CacheAccess accessResponse;
    Directory dirEntry(_pagesInRange);
    Directory dirEvict(_pagesInRange);

    if (_TLB2Size>0) {
        addr=this->appendVPID(addr,appid);

        pthread_mutex_lock(&_mutexDiDi);
        _TLB2Accesses++;
        uint32_t dirPage = this->getPageInDirectory(addr);
        accessResponse=_TLB2[0]->read2(addr,evictPage,dirEvict,dirEntry,nil,
                                       threadid,dirPage);
        if (CacheAccessIsHit(accessResponse)) {
            _TLB2Hits++;
        }
        if (CacheAccessIsHitInvalidate(accessResponse)) {
            _TLB2HitInvalidate++;
        }
        if (CacheAccessIsEvict(accessResponse)) {
            _TLB2Evicted++;
            pthread_mutex_unlock(&_mutexDiDi);
            // appid needs to be recalculated
            // Eviction can be from another application
            appid=getApplicationVPID(evictPage);
            evictTLB1Range(appid,evictPage,dirEvict);
        }
        else {
            pthread_mutex_unlock(&_mutexDiDi);
        }
    }
}
